package thinkingInJava.concurrency;
//: concurrency/restaurant2/RestaurantWithQueues.java
// {Args: 5}
import java.util.concurrent.*;

import thinkingInJava.enumerated.menu.Food;
import thinkingInJava.enumerated.menu.Course;

import java.util.*;
import static thinkingInJava.YTool.YPrint.*;

// This is given to the waiter, who gives it to the chef:
class Order { // (A data-transfer object)
	private static int counter = 0;
	private final int id = counter++;
	private final Customer customer;
	private final WaitPerson6 waitPerson;
	private final Food food;
	public Order(Customer cust, WaitPerson6 wp, Food f) {
		customer = cust;
		waitPerson = wp;
		food = f;
	}
	public Food item() { return food; }
	public Customer getCustomer() { return customer; }
	public WaitPerson6 getWaitPerson() { return waitPerson; }
	public String toString() {
		return "Order: " + id + " item: " + food +
				" for: " + customer +
				" served by: " + waitPerson;
	}
}

// This is what comes back from the chef:
class Plate {
	private final Order order;
	private final Food food;
	public Plate(Order ord, Food f) {
		order = ord;
		food = f;
	}
	public Order getOrder() { return order; }
	public Food getFood() { return food; }
	public String toString() { return food.toString(); }
}

class Customer implements Runnable {
	private static int counter = 0;
	private final int id = counter++;
	private final WaitPerson6 waitPerson;
	// Only one course at a time can be received:
	private SynchronousQueue<Plate> placeSetting =
			new SynchronousQueue<Plate>();
	public Customer(WaitPerson6 w) { waitPerson = w; }
	public void
	deliver(Plate p) throws InterruptedException {
		// Only blocks if customer is still
		// eating the previous course:
		placeSetting.put(p);
	}
	public void run() {
		for(Course course : Course.values()) {
			Food food = course.randomSelection();
			try {
				waitPerson.placeOrder(this, food);
				// Blocks until course has been delivered:
				print(this + "eating " + placeSetting.take());
			} catch(InterruptedException e) {
				print(this + "waiting for " +
						course + " interrupted");
				break;
			}
		}
		print(this + "finished meal, leaving");
	}
	public String toString() {
		return "Customer " + id + " ";
	}
}

class WaitPerson6 implements Runnable {
	private static int counter = 0;
	private final int id = counter++;
	private final Restaurant6 restaurant;
	BlockingQueue<Plate> filledOrders =
			new LinkedBlockingQueue<Plate>();
	public WaitPerson6(Restaurant6 rest) { restaurant = rest; }
	public void placeOrder(Customer cust, Food food) {
		try {
			// Shouldn't actually block because this is
			// a LinkedBlockingQueue with no size limit:
			restaurant.orders.put(new Order(cust, this, food));
		} catch(InterruptedException e) {
			print(this + " placeOrder interrupted");
		}
	}
	public void run() {
		try {
			while(!Thread.interrupted()) {
				// Blocks until a course is ready
				Plate plate = filledOrders.take();
				print(this + "received " + plate +
						" delivering to " +
						plate.getOrder().getCustomer());
				plate.getOrder().getCustomer().deliver(plate);
			}
		} catch(InterruptedException e) {
			print(this + " interrupted");
		}
		print(this + " off duty");
	}
	public String toString() {
		return "WaitPerson6 " + id + " ";
	}
}

class Chef6 implements Runnable {
	private static int counter = 0;
	private final int id = counter++;
	private final Restaurant6 restaurant;
	private static Random rand = new Random(47);
	public Chef6(Restaurant6 rest) { restaurant = rest; }
	public void run() {
		try {
			while(!Thread.interrupted()) {
				// Blocks until an order appears:
				Order order = restaurant.orders.take();
				Food requestedItem = order.item();
				// Time to prepare order:
				TimeUnit.MILLISECONDS.sleep(rand.nextInt(500));
				Plate plate = new Plate(order, requestedItem);
				order.getWaitPerson().filledOrders.put(plate);
			}
		} catch(InterruptedException e) {
			print(this + " interrupted");
		}
		print(this + " off duty");
	}
	public String toString() { return "Chef6 " + id + " "; }
}

class Restaurant6 implements Runnable {
	private List<WaitPerson6> waitPersons =
			new ArrayList<WaitPerson6>();
	private List<Chef6> chefs = new ArrayList<Chef6>();
	private ExecutorService exec;
	private static Random rand = new Random(47);
	BlockingQueue<Order>
	orders = new LinkedBlockingQueue<Order>();
	public Restaurant6(ExecutorService e, int nWaitPersons,
			int nChefs) {
		exec = e;
		for(int i = 0; i < nWaitPersons; i++) {
			WaitPerson6 waitPerson = new WaitPerson6(this);
			waitPersons.add(waitPerson);
			exec.execute(waitPerson);
		}
		for(int i = 0; i < nChefs; i++) {
			Chef6 chef = new Chef6(this);
			chefs.add(chef);
			exec.execute(chef);
		}
	}
	public void run() {
		try {
			while(!Thread.interrupted()) {
				// A new customer arrives; assign a WaitPerson6:
				WaitPerson6 wp = waitPersons.get(
						rand.nextInt(waitPersons.size()));
				Customer c = new Customer(wp);
				exec.execute(c);
				TimeUnit.MILLISECONDS.sleep(100);
			}
		} catch(InterruptedException e) {
			print("Restaurant6 interrupted");
		}
		print("Restaurant6 closing");
	}
}

public class RestaurantWithQueues {
	public static void main(String[] args) throws Exception {
		ExecutorService exec = Executors.newCachedThreadPool();
		Restaurant6 restaurant = new Restaurant6(exec, 5, 2);
		exec.execute(restaurant);
		if(args.length > 0) // Optional argument
			TimeUnit.SECONDS.sleep(new Integer(args[0]));
		else {
			print("Press 'Enter' to quit");
			System.in.read();
		}
		exec.shutdownNow();
	}
} /* Output: (Sample)
WaitPerson6 0 received SPRING_ROLLS delivering to Customer 1
Customer 1 eating SPRING_ROLLS
WaitPerson6 3 received SPRING_ROLLS delivering to Customer 0
Customer 0 eating SPRING_ROLLS
WaitPerson6 0 received BURRITO delivering to Customer 1
Customer 1 eating BURRITO
WaitPerson6 3 received SPRING_ROLLS delivering to Customer 2
Customer 2 eating SPRING_ROLLS
WaitPerson6 1 received SOUP delivering to Customer 3
Customer 3 eating SOUP
WaitPerson6 3 received VINDALOO delivering to Customer 0
Customer 0 eating VINDALOO
WaitPerson6 0 received FRUIT delivering to Customer 1
...
 *///:~
